import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.Map;
import java.util.Scanner;
/**
 * This class generates three files for analysis namely dropPlot.txt, 
 * delayPlot.txt and ratePlot.txt from the sender and receiver log files. 
 * dropPlot: first column has sequence number and second column has the 
 * cumulative number of drops seen until that sequence number on the receiver
 * side
 * delayPlot: first column has sequence number, second column has the relative
 * delay which is the difference in time stamps(in microseconds)for a given 
 * packet and the first packet sent on the sender side and third column has 
 * the relative delay which is the difference in time stamps for a given packet 
 * and the first packet received on the receiver side 
 * ratePlot: first column has the window number, second column has the rate
 * of the packets(in bytes per second) sent on the sender side and the third 
 * column has the rate of the packets received(in bytes per second) received on 
 * the receiver side
 * Note: To generate the rate plot, the rate in bytes per second on sender and
 * receiver side is computed by considering time windows of WINDOW_DURATION in
 * millis. 
 * Rate = total bytes sent/received in that time window duration /WINDOW_DURATION
 * 
 */
public class Analyzer {
	private static final long WINDOW_DURATION = 500; // in milliseconds

	public static void main(String[] args) throws IOException {

		Map<Integer, Long> senderLogMap = new TreeMap<Integer, Long>();
		Map<Integer, Long> receiverLogMap = new TreeMap<Integer, Long>();
		Map<Integer, Long> packetNumVsDepartureTimeMap = new TreeMap<Integer, Long>();
		Map<Integer, Long> packetNumVsArrivalTimeMap = new TreeMap<Integer, Long>();

		Map<Integer, Long> seqNoVsDropCountMap = new TreeMap<Integer, Long>();

		long packetDropCount = 0;
		if (args.length != 5) {
			System.err
					.println("Usage: java Analyzer senderLog receiverLog delayLogOutput dropLogOutput rateLogOutput");
			return;
		}

		Scanner senderLogScanner = new Scanner(new File(args[0]));
		Scanner receiverLogScanner = new Scanner(new File(args[1]));

		long senderFirstTimeStamp = -1;
		long windowStartTimestamp = -1;
		long windowTotalSize = 0;
		long timestamp = -1;
		// List to maintain sender rate samples(bytes/sec)
		List<Long> senderRateList = new ArrayList<Long>();
		while (senderLogScanner.hasNextLine()) {
			String line = senderLogScanner.nextLine();
			String[] log = line.split(",");
			if (log.length != 3) {
				System.err.println("Invalid line in sender log file:" + line);
				System.err.println("Expected:seqNum,timestamp,packetLength");
				return;
			}

			int sequenceNumber = Integer.parseInt(log[0]);
			timestamp = Long.parseLong(log[1]); //in nanoseconds
			int packetLength = Integer.parseInt(log[2]);
			if (senderFirstTimeStamp == -1) {
				senderFirstTimeStamp = timestamp;
				//Initializing windowStartTimestamp to the first timestamp value
				windowStartTimestamp = timestamp;
			}

			long departureTime = timestamp - senderFirstTimeStamp; // relative
																	// to first
																	// packet
																	// that was
																	// sent
			packetNumVsDepartureTimeMap.put(sequenceNumber, departureTime);
			senderLogMap.put(sequenceNumber, timestamp);
			//The difference between timestamp and windowStartTimestamp is in nanoseconds; 
			//WINDOW_DURATION is in millis
			if ((timestamp - windowStartTimestamp) < WINDOW_DURATION * 1000000) {
				//If in the current window, add packet size to the windowTotalSize
				windowTotalSize += packetLength;
			} else {
				//Current window has ended; 
				//calculate the rate and add it to the list of sample rates
				long rate = windowTotalSize * 1000 / WINDOW_DURATION;
				senderRateList.add(rate);
				// move window 
				windowTotalSize = packetLength;
				windowStartTimestamp = timestamp;
			}
		}
		//For remaining packets which are still in the current window
		if((timestamp - windowStartTimestamp) > 0)
		{
			long rate = windowTotalSize * 1000000 / (timestamp - windowStartTimestamp);
			senderRateList.add(rate);
		}

		long receiverFirstTimestamp = -1;
		windowStartTimestamp = -1;
		windowTotalSize = 0;
		timestamp = -1;
		// List to maintain receiver rate samples(bytes/sec)
		List<Long> receiverRateList = new ArrayList<Long>();
		while (receiverLogScanner.hasNextLine()) {
			String line = receiverLogScanner.nextLine();
			String[] log = line.split(",");
			if (log.length != 3) {
				System.err.println("Invalid line in receiver log file:" + line);
				System.err.println("Expected:seqNum,timestamp,packetLength");
				return;
			}
			int sequenceNumber = Integer.parseInt(log[0]);
			timestamp = Long.parseLong(log[1]); //in nanoseconds
			int packetLength = Integer.parseInt(log[2]);

			if (receiverFirstTimestamp == -1) {
				receiverFirstTimestamp = timestamp;
				//Initializing windowStartTimestamp to the first timestamp value
				windowStartTimestamp = timestamp;
			}
			long arrivalTime = timestamp - receiverFirstTimestamp; // relative
																	// to first
																	// packet
																	// that was
																	// received
			packetNumVsArrivalTimeMap.put(sequenceNumber, arrivalTime);
			receiverLogMap.put(sequenceNumber, timestamp);

			//The difference between timestamp and windowStartTimestamp is in nanoseconds; 
			//WINDOW_DURATION is in millis
			if ((timestamp - windowStartTimestamp) < WINDOW_DURATION * 1000000) {
				//If in the current window, add packet size to the windowTotalSize
				windowTotalSize += packetLength;
			} else {
				//Current window has ended; 
				//calculate the rate and add it to the list of sample rates
				long rate = windowTotalSize * 1000 / WINDOW_DURATION;
				receiverRateList.add(rate);
				// move window
				windowTotalSize = packetLength;
				windowStartTimestamp = timestamp;
			}
		}
		//For remaining packets which are still in the current window
		if((timestamp - windowStartTimestamp) > 0)
		{
			long rate = windowTotalSize * 1000000 / (timestamp - windowStartTimestamp);
			receiverRateList.add(rate);
		}

		for (int seqNo : senderLogMap.keySet()) {
			if (!receiverLogMap.containsKey(seqNo)) {
				packetDropCount++;
				seqNoVsDropCountMap.put(seqNo, packetDropCount);
			}
		}

		File delayLog = new File(args[2]);
		writeToFile(packetNumVsDepartureTimeMap, packetNumVsArrivalTimeMap,
				delayLog);

		File packetDropLog = new File(args[3]);
		writeToFile(seqNoVsDropCountMap, packetDropLog);

		String rateLogFileName = args[4];
		writeRateLog(senderRateList, receiverRateList, rateLogFileName);
		
		System.out.println("Done writing output files");
	}

	/*
	 * This method writes to the rateLogFile
	 */
	private static void writeRateLog(List<Long> senderRateList,
			List<Long> receiverRateList, String rateLogFileName)
			throws IOException {
		File senderRateLog = new File(rateLogFileName);
		PrintWriter rateOut = new PrintWriter(new FileWriter(senderRateLog));

		int rateIndex = 0;
		//Write the sender and receiver rate samples to the log file 
		for (rateIndex = 0; rateIndex < senderRateList.size()
				&& rateIndex < receiverRateList.size(); rateIndex++) {
			rateOut.println(rateIndex + " " + senderRateList.get(rateIndex)
					+ " " + receiverRateList.get(rateIndex));
		}
		if (rateIndex < senderRateList.size()) {
			//If the number of sender rate samples is greater than the number of 
			//receiver rate samples
			for (; rateIndex < senderRateList.size(); rateIndex++) {
				rateOut.println(rateIndex + " " + senderRateList.get(rateIndex)
						+ " 0");
			}
		} else if (rateIndex < receiverRateList.size()) {
			for (; rateIndex < receiverRateList.size(); rateIndex++) {
				rateOut.println(rateIndex + " 0 "
						+ receiverRateList.get(rateIndex));
			}
		}
		rateOut.close();
	}

	protected static void writeToFile(Map<Integer, Long> map1,
			Map<Integer, Long> map2, File log) throws IOException {
		PrintWriter out = new PrintWriter(new FileWriter(log));
		for (int seqNo : map1.keySet()) {
			long t2 = (map2.containsKey(seqNo) ? map2.get(seqNo) : -1);
			out.println(seqNo + " " + map1.get(seqNo) + " " + t2);
		}
		out.close();
	}

	protected static void writeToFile(Map<Integer, Long> map, File log)
			throws IOException {
		PrintWriter out = new PrintWriter(new FileWriter(log));
		for (int seqNo : map.keySet()) {
			out.println(seqNo + " " + map.get(seqNo));
		}
		out.close();
	}

}
